feat: per-card AI tool approval pills#1091
Merged
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
1 similar comment
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This was referenced May 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR E from
docs/refactor/ai-chat-redesign.md. Replaces the global safe-modeNSAlertfor AI write/destructive tool calls with inline approval pills on each tool-use card:[Run] [Always for this connection ▾] [Cancel].Behaviour matrix:
Always for this connectionwrites the tool name intoDatabaseConnection.aiAlwaysAllowedTools(new persisted Set). Wired through all five persistence layers:DatabaseConnection.Codable,StoredConnectionon-disk,duplicateConnection,SyncRecordMapperfor CloudKit, and the connection form UI (extension to existing AI Rules pane).Cancelends only that tool call; the assistant gets an error result and continues. The streaming task itself stays alive.AIChatViewModel.cancelStreamnow also drains pending approvals viaToolApprovalCenter.cancelAll().Architecture
Three new files plus an extension:
ToolApprovalCenter(MainActor):awaitDecision(toolUseId)registers aCheckedContinuation<ToolApprovalDecision, Never>;resolve(toolUseId, decision)resumes it. The view buttons callresolve; the viewmodel callsawaitDecision.ToolApprovalStateenum onToolUseBlock:approved,pending,denied(reason),cancelled. Codable-forward-compatible (decodeIfPresent ?? .approved).ToolApprovalActionsRowSwiftUI view: the three buttons..borderedProminentRun,.borderlessButtonAlways menu,.borderedCancel. Native HIG, no custom chrome.AIChatViewModel+ToolApproval: extension carrying the newresolveAndAwaitApprovals,synthesizeResults, and approval-state mutation helpers (extracted because the main file was at the SwiftLint type body length warning threshold).AIChatViewModel.runStreamwas restructured. Previously:executeToolUses → appendToolRoundtrip(toolUseBlocks + results). Now:assemble → resolveAndAwaitApprovals (appends pending blocks, awaits decisions) → executeToolUses (only approved) → synthesizeResults → completeToolRoundtrip. Tool cards now appear before execution, not after.ExecuteQueryChatToolandConfirmDestructiveOperationChatToolno longer callMCPAuthPolicy.checkSafeModeDialog. The viewmodel layer covers approval and Read-Only blocking. The MCP server tools (separate files) keepcheckSafeModeDialogsince external clients have no card UI.Out of scope, observed in passing
The PR G
aiRulesfield onDatabaseConnectionis currently wired intoCodablebut absent fromStoredConnectionandSyncRecordMapper. It silently drops on save/reload and does not sync to iCloud. This is unrelated to PR E and should land as a separate hotfix.Files modified
Core/AI/Chat/ChatTurn.swift—ToolApprovalState+ ToolUseBlock fieldCore/AI/Chat/ToolApprovalCenter.swift(new) — continuation registryCore/AI/Chat/ChatToolRegistry.swift—requiresApproval(toolName:)Core/AI/Chat/Tools/ExecuteQueryChatTool.swift— dropcheckSafeModeDialogCore/AI/Chat/Tools/ConfirmDestructiveOperationChatTool.swift— dropcheckSafeModeDialogModels/Connection/DatabaseConnection.swift—aiAlwaysAllowedTools: Set<String>Core/Storage/ConnectionStorage.swift—StoredConnection.aiAlwaysAllowedToolsround-tripCore/Sync/SyncRecordMapper.swift— CloudKit fieldViewModels/AIChatViewModel.swift— restructuredrunStream,cancelStreamcancels pending approvalsViewModels/AIChatViewModel+ToolApproval.swift(new) — approval lifecycle methodsViews/AIChat/AIChatToolUseBlockView.swift— pending state label, renderToolApprovalActionsRowwhen pendingViews/AIChat/ToolApprovalActionsRow.swift(new) — three-button HIG rowCHANGELOG.md— Added entry under [Unreleased]Test plan
swiftlint --strictclean on all touched files.Confirm Writes. Switch chat mode toEditorAgent.update users set is_active = true. The tool card appears withRun / Always / Cancelpills. NoNSAlertmodal.Run. Query executes. Card swaps to running state, then result block appears.Always for this connection. Query executes. Inspect connection settings:execute_queryis whitelisted.Read Only. Ask the AI todelete from users. Tool card showsBlocked, no pills.Silent. Ask write. Auto-runs, no pills.list_tables,describe_table) never show pills regardless of safe mode.